package com.hirsi.common.core.aspect;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hirsi.common.core.exception.ServerRuntimeException;
import com.hirsi.common.core.util.AccessTokenUtil;
import com.hirsi.common.core.util.HttpContextUtil;
import com.hirsi.common.db.log.entity.LogRest;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.LocalDateTime;

/**
 * @author 封夕
 * @since 2023/3/17 19:50
 */
@Aspect
@Component
@Slf4j
public class RestRequestLogAspect {

    @Value("${spring.application.id}")
    private Integer applicationId;

    /**
     * 请求开始时的时间戳
     * 用来计算请求时长
     */
    private ThreadLocal<Long> beforeTimeStamp = new ThreadLocal<>();

    private ThreadLocal<LogRest> beforeRestLog = new ThreadLocal<>();

    @Pointcut("(@annotation(org.springframework.web.bind.annotation.RequestMapping) || @annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.PostMapping)) && within(com.hirsi..*) && !@annotation(com.hirsi.common.core.annotation.IgnoreRestLog)")
    public void pointCut() {
    }

    ;

    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String url = HttpContextUtil.getUrl();
        if ("/error".equals(url)) return;
        Signature signature = joinPoint.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        Method method = ((MethodSignature) signature).getMethod();
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        //设置请求开始时间（毫秒）
        beforeTimeStamp.set(System.currentTimeMillis());
        LogRest restLog = new LogRest();
        if (apiOperation != null && StrUtil.isNotBlank(apiOperation.value()))
            restLog.setDescription(apiOperation.value());
        restLog.setUrl(url)
                .setClazz(String.format("%s.%s", className, methodName))
                .setParam(JSONUtil.toJsonStr(HttpContextUtil.getParam()))
                .setBody(JSONUtil.toJsonStr(joinPoint.getArgs()))
                .setMethod(HttpContextUtil.getMethod())
                .setIp(HttpContextUtil.getIpAddr())
                .setUserId(AccessTokenUtil.getUserId())
                .setToken(AccessTokenUtil.getAccessToken())
                .setRequestTime(LocalDateTime.now());
        restLog.setApplicationId(applicationId);
        beforeRestLog.set(restLog);
    }

    @AfterReturning(value = "pointCut()", returning = "result")
    public void afterReturning(Object result) {
        Long timeStamp = beforeTimeStamp.get();
        LogRest restLog = beforeRestLog.get();
        if (timeStamp != null && restLog != null) {
            long consumeTime = System.currentTimeMillis() - timeStamp;
            restLog.setTimes((int) consumeTime);
            restLog.setStatus(HttpStatus.OK.value());
            restLog.setCode(200);
            try {
                restLog.insert();
            } catch (Exception ex) {
                log.error("日志保存失败！", ex);
            } finally {
                beforeTimeStamp.remove();
                beforeRestLog.remove();
            }
        }
    }

    @AfterThrowing(value = "pointCut()", throwing = "e")
    public void afterThrowing(Throwable e) {
        Long timeStamp = beforeTimeStamp.get();
        LogRest restLog = beforeRestLog.get();
        if (timeStamp != null && restLog != null) {
            long consumeTime = System.currentTimeMillis() - timeStamp;
            restLog.setTimes((int) consumeTime);
            restLog.setErrorMsg(ExceptionUtil.getMessage(e));
            if (e instanceof ServerRuntimeException) {
                restLog.setStatus(((ServerRuntimeException) e).getStatus().value())
                        .setCode(((ServerRuntimeException) e).getCode());
            } else {
                restLog.setStatus(500);
            }
            try {
                restLog.insert();
            } catch (Exception ex) {
                log.error("日志保存失败！", e);
            } finally {
                beforeTimeStamp.remove();
                beforeRestLog.remove();
            }
        }
    }
}
